using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using LoadGenerator;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading;
using System.Web.Script.Serialization;
using System.Text;
using System.Globalization;
using System.Web.Services;
using LoadController;
using System.IO;
using System.IO.Compression;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.Practices.EnterpriseLibrary.WindowsAzure.Autoscaling.ServiceManagement.Contracts;

namespace LoadController
{
    public class WebRole : RoleEntryPoint
    {
        private Result result;
        CloudTable ActiveJobTable = AzureTableHelper.GetCloudTable(AppSettings.Table_ActiveJob);
        private CloudTable SamplesTable = AzureTableHelper.GetCloudTable(AppSettings.Table_Samples);
        private const int SAMPLE_RATE = 1000;
        private AutoScaler scaler;
        private DateTimeOffset lastSampleTimeStamp;
        
        
        public override bool OnStart()
        {
            //check if tables and blob containers exist
            try
            {
                AppSettings.CreateTablesIfNotExist();
                AppSettings.CreateBlobContainersIfNotExist();
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Can't create tables or blob containers.");
                Trace.WriteLine(ex.ToString());
            }

            scaler = new AutoScaler();
            return base.OnStart();
        }

        

        public override void Run()
        {
            Trace.WriteLine("LoadController entry point called", "Information");
           
            while (true)
            {
                Thread.Sleep(1000);
                Trace.WriteLine("Retrieving Job", "Information");

                var activeJob = AzureTableHelper.GetCloudTable(AppSettings.Table_ActiveJob).ExecuteQuery<Job>(new TableQuery<Job>()).ToList<Job>();
                if (activeJob.Count() == 0)
                {
                    var queuedJobs = AzureTableHelper.GetCloudTable(AppSettings.Table_Queue).ExecuteQuery<Job>(new TableQuery<Job>()).ToList<Job>();

                    if (queuedJobs.Count() > 0)
                    {
                        Job job = queuedJobs.Last<Job>();

                        int currentInstanceCount = RoleEnvironment.Roles["LoadGenerator"].Instances.Count();

                        if (currentInstanceCount == job.InstanceCount)
                        {
                            if (scaler.getDeployment().Status == DeploymentStatus.Running)
                            {
                                CleanSamplesTable();
                                StartGenerators(job);
                                EvaluateResult(job);
                            }                   
                        }
                        else
                        {
                            Trace.WriteLine("Change Deployment", "Information");
                                
                            scaler.ChangeConfiguration(job.InstanceCount);
                        }
                    }
                }
                else
                {
                    Trace.WriteLine("LoadController crashed. Abort all Generators","Error");
                    LoadGenerator.RoleCommunicator.AbortActiveJob();
                }
            }
        }

        private void CleanSamplesTable()
        {
            try
            {
                List<Sample> samples = SamplesTable.ExecuteQuery<Sample>(new TableQuery<Sample>()).ToList<Sample>();
                foreach (var item in samples)
                {
                    SamplesTable.Execute(TableOperation.Delete(item));
                }
            }
            catch (Exception)
            {
                
            }
        }


        private void StartGenerators(Job job)
        {
            Trace.WriteLine("Start Generators", "Information");
            AzureTableHelper.GetCloudTable(AppSettings.Table_Queue).Execute(TableOperation.Delete(job));

            foreach (var instance in RoleEnvironment.Roles["LoadGenerator"].Instances)
            {
                var jobPK = job.PartitionKey;
                job.PartitionKey += "_" + "IN_" + instance.Id.Split(new char[] { '_' }).Last();
                AzureTableHelper.GetCloudTable(AppSettings.Table_ActiveJob).Execute(TableOperation.Insert(job));
                job.PartitionKey = jobPK;
            }           
        }



        private void EvaluateResult(Job job)
        {
            result = new Result()              
            {                               
                JobName = job.RowKey,     
                Duration = job.Duration,   
                ServiceName = job.ServiceName,                
            };

            result.SetStarted();

            while (ActiveJobTable.ExecuteQuery(new TableQuery<Job>()).Count() != 0) 
            {
                Trace.WriteLine("Evaluating Results", "Information");
                ConvertSamplesToResult(job);
                Thread.Sleep(SAMPLE_RATE);
            }

              
            result.SetFinished();
            ConvertSamplesToResult(job);
            ResultSerializer.SerializeAndSave(result, job.PartitionKey);
            
            FinishJob(job);
        }

        
        private void ConvertSamplesToResult(Job job)
        {
            List<Sample> RetrievedSamples = SamplesTable.ExecuteQuery<Sample>(new TableQuery<Sample>()).ToList<Sample>();

            if (RetrievedSamples.Count > 0)
            {                
                List<Sample> latestSamples = RetrievedSamples.Where(x => x.Timestamp > lastSampleTimeStamp).ToList<Sample>();

                if (latestSamples.Count > 0)
                {
                    result.SummedAvgThreadCount = RetrievedSamples.Count;
                    result.Update(latestSamples);
                    ResultSerializer.SerializeAndSave(result, job.PartitionKey);
                    lastSampleTimeStamp = RetrievedSamples.OrderBy(x => x.Timestamp).Last().Timestamp;
                }              
            }

        }

        private void FinishJob(Job job)
        {            
            AzureTableHelper.GetCloudTable(AppSettings.Table_FinishedJobs).Execute(TableOperation.Insert(job));

            //Delete Assembly from blob
            CloudBlobContainer containerAss = AzureBlobHelper.GetContainerReference(AppSettings.BlobContainer_assemblies);
            AzureBlobHelper.DeleteBlob(containerAss, job.DllName);
        }
    }


    public class Result
    {
        public string JobName { get; set; }
        public int Duration { get; set; }
        public string ServiceName { get; set; }
      
        public long StartTicks { get; set; }
        private long EndTicks { get; set; }

        public int SummedAvgThreadCount { get; set; } 
        public int TotalCount { get; set; }
        public int LastAvgResponseTime { get; set; }
        public int MaxResponseTime { get; set; }
        public int Errors { get; set; }
        public List<string[]> ChartSeries = new List<string[]>();

        public List<InstanceResult> InstanceResults = new List<InstanceResult>();
        
      
        public string StartedDateTime { get; set; }
        public string EndedDateTime { get; set; }
        public string RemainingTime { get; set; }
        public double Progress { get; set; }

        public bool JobFinished { get; set; }
        
        
               
        public Result()
        {
        }

        public void Update(List<Sample> samples)
        {
            
            
            //InstanceResults
            foreach (var InstanceSamples in samples.GroupBy(x => x.PartitionKey))
            {

                var InsRes = this.InstanceResults.Where(x => x.InstanceName == InstanceSamples.First().PartitionKey).SingleOrDefault();

                if (InsRes != null)
                {
                    InsRes.Count += InstanceSamples.Count();
                    InsRes.LastAvgResponseTime = (int)Math.Round(InstanceSamples.Average(x => x.ResponseTime));
                    InsRes.Errors += InstanceSamples.Where(x => x.hasError == true).Count();
                    InsRes.AvgThreadCount = (int)Math.Round(InstanceSamples.Average(x => x.ThreadCount));
                }
                else
                {
                    InsRes = new InstanceResult()
                    {
                        InstanceName = InstanceSamples.First().PartitionKey,
                        Count = InstanceSamples.Count(),
                        LastAvgResponseTime = (int)Math.Round(InstanceSamples.Average(x => x.ResponseTime)),
                        Errors = InstanceSamples.Where(x => x.hasError == true).Count(),
                        AvgThreadCount = (int)Math.Round(InstanceSamples.Average(x => x.ThreadCount))
                    };

                    this.InstanceResults.Add(InsRes);
                }
                               
                   
            }

                                    
            //Global
            this.TotalCount += samples.Count;
            this.LastAvgResponseTime = (int)Math.Round(samples.Average(x => x.ResponseTime));
            
            
            int tempMax = samples.Max(x => x.ResponseTime);
            if (tempMax > this.MaxResponseTime)  this.MaxResponseTime = tempMax;

            this.Errors += samples.Where(x => x.hasError == true).Count();
            int currentErrors = samples.Where(x => x.hasError == true).Count();

            long updateTicks = DateTime.UtcNow.Ticks;
            string yParts = this.LastAvgResponseTime.ToString() + ";" + this.SummedAvgThreadCount.ToString() + ";" + currentErrors.ToString();
            ChartSeries.Add(new string[] { GetJSSDateString(updateTicks), yParts });

            EvaluateProgress(updateTicks);              
        }

        public void EvaluateProgress(long ticks)
        {
            long duration = this.Duration * TimeSpan.TicksPerMinute;
            long pronEnd = StartTicks + duration;

            if (pronEnd >= ticks)
            {
                long span = pronEnd - ticks;
                DateTime spanDate = new DateTime(span);
                String result = spanDate.ToString("HH:mm:ss");
                this.RemainingTime = result;

                var onePercent = ((double)100 / duration);
                double progress = onePercent * (duration - span);
                this.Progress = Math.Round(progress);
            }
        }

        public void SetStarted()
        {
            var ticks = DateTime.UtcNow.Ticks;
            this.StartTicks = ticks;
            this.StartedDateTime = GetDateTimeString(ticks);
            JobFinished = false;
        }


        public void SetFinished()
        {
            this.Progress = 100;
            this.RemainingTime = "00:00:00";
            this.EndedDateTime = GetDateTimeString(DateTime.UtcNow.Ticks);
            JobFinished = true;
        }

        public string GetJSSDateString(long ticks)
        {
            DateTime date = new DateTime(ticks);

            string result = date.Year + ";" + date.Month + ";"
                + date.Day + ";" + date.Hour + ";" + date.Minute + ";" + date.Second + ";" + date.Millisecond;
            return result;
        }

        public string GetDateTimeString(long ticks)
        {
            DateTime date = new DateTime(ticks);
            String result = date.ToString("G");
            return result;
        }
    }

    public class InstanceResult
    {
        public string InstanceName { get; set; }
        public int Count { get; set; }
        public int LastAvgResponseTime { get; set; }
        public int Errors { get; set; }
        public int AvgThreadCount { get; set; }

        public InstanceResult() { }
    }

    public class ResultSerializer
    {
        public static void SerializeAndSave(Result result, string blobName)
        {
            JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
            string serializedResult = jsSerializer.Serialize(result);
            var compressedResult = Compress(serializedResult);

            AzureBlobHelper.UploadBlobIntoContainer(AppSettings.BlobContainer_results, blobName, compressedResult);
        }

        public static string Serialize(Result result)
        {
            JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
            string serializedResult = jsSerializer.Serialize(result);
            return serializedResult;
        }

        public static Result Deserialize(byte[] byteArr)
        {
            JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
            string serializedResult = Decompress(byteArr);
            return (Result)jsSerializer.Deserialize(serializedResult, typeof(Result));
        }

        public static byte[] Compress(string text)
        {
            byte[] data = Encoding.UTF8.GetBytes(text);
            var stream = new MemoryStream();

            using (Stream ds = new GZipStream(stream, CompressionMode.Compress))
            {
                ds.Write(data, 0, data.Length);
            }

            byte[] compressed = stream.ToArray();

            return compressed;
        }

        public static string Decompress(byte[] gzip)
        {
            using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
            {
                const int size = 4096;
                byte[] buffer = new byte[size];
                using (MemoryStream memory = new MemoryStream())
                {
                    int count = 0;
                    do
                    {
                        count = stream.Read(buffer, 0, size);
                        if (count > 0)
                        {
                            memory.Write(buffer, 0, count);
                        }
                    }
                    while (count > 0);

                    return Encoding.UTF8.GetString(memory.ToArray());
                }
            }
        }
    }


}












